#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016-2018, Niklas Hauser
# Copyright (c) 2017, Fabian Greif
#
# This file is part of the modm project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# -----------------------------------------------------------------------------

from collections import OrderedDict

def port_ranges(gpios):
    ports = {p: (8, 0) for p in set([p["port"] for p in gpios])}
    for gpio in gpios:
        pin = int(gpio["pin"])
        pmin, pmax = ports[gpio["port"]]
        ports[gpio["port"]] = (min(pin, pmin), max(pin, pmax))

    ports = [{"name": k.upper(), "start": v[0], "width": v[1] - v[0] + 1} for k,v in ports.items()]
    ports.sort(key=lambda p: p["name"])
    return ports

def get_driver(s):
    name = s["driver"].capitalize()
    if "instance" in s: name += s["instance"].capitalize();
    return name

def get_name(s):
    return s["name"].capitalize()

def extract_signals(signals):
    afs = {}
    for s in signals:
        driver = get_driver(s)
        if "Exint" in driver: continue;
        name = get_name(s)
        if name.isdigit():
            if "Adc" in driver:
                name = "In" + name
        key = driver + name
        afs[key] = {"driver": driver, "name": name}
    return afs

def init(module):
    module.name = ":platform:gpio"
    module.description = FileReader("module.md")

def prepare(module, options):
    if not options[":target"].has_driver("gpio:avr"):
        return False

    # port.hpp depends on modm::bitReverse()!
    module.depends(":architecture:gpio", ":math:utils")
    return True

bprops = {}
def validate(env):
    device = env[":target"]
    driver = device.get_driver("gpio")

    all_signals = {}
    for gpio in driver["gpio"]:
        key = gpio["port"] + gpio["pin"]
        # deal with all signals
        signals = gpio["signal"] if "signal" in gpio else []
        extracted_signals = extract_signals(signals)
        all_signals.update(extracted_signals)
        signal_names = sorted(list(set(s["name"] for s in extracted_signals.values())))
        extracted_signals = OrderedDict([(name, sorted([s for s in extracted_signals.values() if s["name"] == name], key=lambda si:si["name"])) for name in signal_names])
        # extract information on external interrupts
        exints = list(filter(lambda p: p["driver"] == "exint", signals))
        pcint = list(filter(lambda p: p["name"].startswith("pcint"), exints))
        extint = list(filter(lambda p: not p["name"].startswith("pcint"), exints))
        if len(pcint) > 1: raise ValidateException("More than one Pin Change interrupt on pin '{}'!".format(key.upper()));
        if len(extint) > 1: raise ValidateException("More than one External interrupt on pin '{}'!".format(key.upper()));
        bprops[key] = {
            "gpio": gpio,
            "pcint": int(pcint[0]["name"].replace("pcint", "")) if len(pcint) else -1,
            "extint": int(extint[0]["name"]) if len(extint) else -1,
            "signals": extracted_signals
        }

    if not len(all_signals):
        raise ValidateException("The device file for '{}' does not contain any signals!".format(device.partname))

    # check if any signal names start with a digit
    faults = [(af["driver"], af["name"]) for af in all_signals.values() if af["name"].isdigit()]
    if len(faults):
        raise ValidateException("These signal names are starting with a digit!\n{}".format(
                ", ".join(["{}::{}".format(d, n) for (d, n) in faults])))

    all_peripherals = [s["driver"] for s in all_signals.values()]
    # Signals are not enough, since there are peripherals that don't have signals.
    # Example: STM32F401RE < 64pins: SPI4 cannot be connected to any pins.
    # FIXME: Allow accessing device file more freely
    all_drivers = [d for d in device._properties["driver"] if d["name"] not in ["gpio", "core"]]
    for d in all_drivers:
        dname = d["name"].capitalize()
        if "instance" in d:
            all_peripherals.extend([dname + i.capitalize() for i in d["instance"]])
        else:
            all_peripherals.append(dname)
    bprops["all_peripherals"] = sorted(list(set(all_peripherals)))
    bprops["all_signals"] = sorted(list(set(s["name"] for s in all_signals.values())))
    bprops["ranges"] = port_ranges(driver["gpio"])
    bprops["ports"] = {k:i for i, k in enumerate([p["name"] for p in bprops["ranges"]])}
    bprops["gpios"] = [bprops[g["port"] + g["pin"]] for g in driver["gpio"]]
    bprops["port_width"] = 8

def build(env):
    device = env[":target"]
    driver = device.get_driver("gpio")
    properties = {}
    properties["target"] = target = device.identifier
    properties["driver"] = driver
    properties.update(bprops)

    if (target["family"] == "mega" and target["name"] in ["8", "16", "32", "64", "128", "162", "8515", "8535"]) or \
            (target["family"] == "tiny" and target["name"] in ["26"]):
        properties["notoggle"] = True
    else:
        properties["notoggle"] = False

    if target["family"] == "tiny" and target["name"] in ["4", "5", "9", "10", "20", "40", "828", "1634"]:
        properties["pue"] = True
    else:
        properties["pue"] = False

    if ((target.family == "mega" and target.name in ["8", "16", "32", "162", "8515", "8535"]) or
        (target.family == "tiny" and target.name in ["13", "24", "25", "40", "44", "45", "84", "85", "261", "461", "861", "441", "841"])):
        properties["eicra"] = "MCUCR"
    else:
        properties["eicra"] = "EICRA"

    if target.family == "mega" and target.name in ["8", "16", "32", "8515", "8535"] and target["type"] not in ["u2", "u4", "u4rc"]:
        properties["isc2"] = "MCUCSR"
    if target.family == "mega" and target.name in ["162"]:
        properties["isc2"] = "EMCUCR"

    env.substitutions = properties
    env.outbasepath = "modm/src/modm/platform/gpio"

    env.copy("define.h")
    env.template("data.hpp.in")
    env.template("../common/pins.hpp.in", "pins.hpp")
    env.template("static.hpp.in")
    env.template("base.hpp.in")
    env.template("port.hpp.in")
    env.template("software_port.hpp.in")
    env.template("set.hpp.in")
    env.template("../common/unused.hpp.in", "unused.hpp")
    env.template("../common/port_shim.hpp.in", "port_shim.hpp")
    env.template("../common/connector.hpp.in", "connector.hpp",
                 filters={"formatPeripheral": "", "printSignalMap": ""})
    env.copy("../common/open_drain.hpp", "open_drain.hpp")
    env.copy("../common/inverted.hpp", "inverted.hpp")
